<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Java Agent 使用指南 | 小鹏杂谈</title>
    <meta name="generator" content="VuePress 1.8.2">
    <link rel="icon" href="/favicon.ico">
    <meta name="description" content="小鹏杂谈">
    <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
    
    <link rel="preload" href="/assets/css/0.styles.8516fda1.css" as="style"><link rel="preload" href="/assets/js/app.b9ccfc55.js" as="script"><link rel="preload" href="/assets/js/4.8390fbcb.js" as="script"><link rel="preload" href="/assets/js/1.3334f995.js" as="script"><link rel="preload" href="/assets/js/16.b75d2036.js" as="script"><link rel="prefetch" href="/assets/js/10.8eb4e356.js"><link rel="prefetch" href="/assets/js/11.31befa4c.js"><link rel="prefetch" href="/assets/js/12.da37eb75.js"><link rel="prefetch" href="/assets/js/13.b24ba73c.js"><link rel="prefetch" href="/assets/js/14.3bbed283.js"><link rel="prefetch" href="/assets/js/15.fac60f8d.js"><link rel="prefetch" href="/assets/js/17.116317c5.js"><link rel="prefetch" href="/assets/js/18.80fdbeda.js"><link rel="prefetch" href="/assets/js/19.459fc46f.js"><link rel="prefetch" href="/assets/js/20.452f30f9.js"><link rel="prefetch" href="/assets/js/21.99f641dc.js"><link rel="prefetch" href="/assets/js/22.081dc33b.js"><link rel="prefetch" href="/assets/js/23.d3455a59.js"><link rel="prefetch" href="/assets/js/24.8f5d39e0.js"><link rel="prefetch" href="/assets/js/25.c3dc47fb.js"><link rel="prefetch" href="/assets/js/26.d93b5cf2.js"><link rel="prefetch" href="/assets/js/27.6d2089a5.js"><link rel="prefetch" href="/assets/js/28.57336847.js"><link rel="prefetch" href="/assets/js/29.903b5e06.js"><link rel="prefetch" href="/assets/js/3.32100170.js"><link rel="prefetch" href="/assets/js/30.274666a3.js"><link rel="prefetch" href="/assets/js/31.fa9bd0f8.js"><link rel="prefetch" href="/assets/js/32.20179720.js"><link rel="prefetch" href="/assets/js/33.2dcc611d.js"><link rel="prefetch" href="/assets/js/34.ba79122c.js"><link rel="prefetch" href="/assets/js/35.bcd9298f.js"><link rel="prefetch" href="/assets/js/36.bf6245d9.js"><link rel="prefetch" href="/assets/js/37.734eb893.js"><link rel="prefetch" href="/assets/js/38.26999070.js"><link rel="prefetch" href="/assets/js/39.cc1f016d.js"><link rel="prefetch" href="/assets/js/40.da84c263.js"><link rel="prefetch" href="/assets/js/41.a96b8efa.js"><link rel="prefetch" href="/assets/js/42.d586cccb.js"><link rel="prefetch" href="/assets/js/43.69bb61aa.js"><link rel="prefetch" href="/assets/js/44.3be5a840.js"><link rel="prefetch" href="/assets/js/45.208a3bc3.js"><link rel="prefetch" href="/assets/js/46.2d6cacad.js"><link rel="prefetch" href="/assets/js/47.5add378a.js"><link rel="prefetch" href="/assets/js/48.587139cc.js"><link rel="prefetch" href="/assets/js/49.57f7c25a.js"><link rel="prefetch" href="/assets/js/5.ff1e84d1.js"><link rel="prefetch" href="/assets/js/50.efde6e4a.js"><link rel="prefetch" href="/assets/js/51.c9447e5f.js"><link rel="prefetch" href="/assets/js/52.57a51c35.js"><link rel="prefetch" href="/assets/js/53.4be0f240.js"><link rel="prefetch" href="/assets/js/54.8dabd349.js"><link rel="prefetch" href="/assets/js/55.3b8ae0e4.js"><link rel="prefetch" href="/assets/js/56.07e830b2.js"><link rel="prefetch" href="/assets/js/57.8c0dbdcc.js"><link rel="prefetch" href="/assets/js/58.63387df6.js"><link rel="prefetch" href="/assets/js/59.959bb8c8.js"><link rel="prefetch" href="/assets/js/6.56a2b5cd.js"><link rel="prefetch" href="/assets/js/60.1041d86f.js"><link rel="prefetch" href="/assets/js/61.f58e6c18.js"><link rel="prefetch" href="/assets/js/62.a1a460c7.js"><link rel="prefetch" href="/assets/js/63.6c611c7d.js"><link rel="prefetch" href="/assets/js/64.6711119c.js"><link rel="prefetch" href="/assets/js/65.4655c3b1.js"><link rel="prefetch" href="/assets/js/66.e5dfe864.js"><link rel="prefetch" href="/assets/js/67.37515de0.js"><link rel="prefetch" href="/assets/js/68.0c8ba92d.js"><link rel="prefetch" href="/assets/js/69.52121d38.js"><link rel="prefetch" href="/assets/js/7.6f143d9f.js"><link rel="prefetch" href="/assets/js/70.1ab4d6a1.js"><link rel="prefetch" href="/assets/js/71.dc99dd1d.js"><link rel="prefetch" href="/assets/js/72.02e21e52.js"><link rel="prefetch" href="/assets/js/73.21efc121.js"><link rel="prefetch" href="/assets/js/74.649f4e1d.js"><link rel="prefetch" href="/assets/js/75.331c13e7.js"><link rel="prefetch" href="/assets/js/76.a94b5ff0.js"><link rel="prefetch" href="/assets/js/8.d4d40f2a.js"><link rel="prefetch" href="/assets/js/9.1b8824e7.js">
    <link rel="stylesheet" href="/assets/css/0.styles.8516fda1.css">
  </head>
  <body>
    <div id="app" data-server-rendered="true"><div class="theme-container no-sidebar" data-v-1156296a><div data-v-1156296a><div id="loader-wrapper" class="loading-wrapper" data-v-d48f4d20 data-v-1156296a data-v-1156296a><div class="loader-main" data-v-d48f4d20><div data-v-d48f4d20></div><div data-v-d48f4d20></div><div data-v-d48f4d20></div><div data-v-d48f4d20></div></div> <!----> <!----></div> <div class="password-shadow password-wrapper-out" style="display:none;" data-v-4e82dffc data-v-1156296a data-v-1156296a><h3 class="title" data-v-4e82dffc data-v-4e82dffc>小鹏杂谈</h3> <p class="description" data-v-4e82dffc data-v-4e82dffc>小鹏杂谈</p> <label id="box" class="inputBox" data-v-4e82dffc data-v-4e82dffc><input type="password" value="" data-v-4e82dffc> <span data-v-4e82dffc>Konck! Knock!</span> <button data-v-4e82dffc>OK</button></label> <div class="footer" data-v-4e82dffc data-v-4e82dffc><span data-v-4e82dffc><i class="iconfont reco-theme" data-v-4e82dffc></i> <a target="blank" href="https://vuepress-theme-reco.recoluan.com" data-v-4e82dffc>vuePress-theme-reco</a></span> <span data-v-4e82dffc><i class="iconfont reco-copyright" data-v-4e82dffc></i> <a data-v-4e82dffc><span data-v-4e82dffc>lzpeng723</span>
            
          <span data-v-4e82dffc>2021 - </span>
          2022
        </a></span></div></div> <div class="hide" data-v-1156296a><header class="navbar" data-v-1156296a><div class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div> <a href="/" class="home-link router-link-active"><img src="/logo.png" alt="小鹏杂谈" class="logo"> <span class="site-name">小鹏杂谈</span></a> <div class="links"><div class="color-picker"><a class="color-button"><i class="iconfont reco-color"></i></a> <div class="color-picker-menu" style="display:none;"><div class="mode-options"><h4 class="title">Choose mode</h4> <ul class="color-mode-options"><li class="dark">dark</li><li class="auto active">auto</li><li class="light">light</li></ul></div></div></div> <div class="search-box"><i class="iconfont reco-search"></i> <input aria-label="Search" autocomplete="off" spellcheck="false" value=""> <!----></div> <nav class="nav-links can-hide"><div class="nav-item"><a href="/" class="nav-link"><i class="iconfont reco-home"></i>
  首页
</a></div><div class="nav-item"><div class="dropdown-wrapper"><a class="dropdown-title"><span class="title"><i class="iconfont reco-category"></i>
      分类
    </span> <span class="arrow right"></span></a> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/categories/运维/" class="nav-link"><i class="undefined"></i>
  运维
</a></li><li class="dropdown-item"><!----> <a href="/categories/开发/" class="nav-link"><i class="undefined"></i>
  开发
</a></li><li class="dropdown-item"><!----> <a href="/categories/后端/" class="nav-link"><i class="undefined"></i>
  后端
</a></li><li class="dropdown-item"><!----> <a href="/categories/前端/" class="nav-link"><i class="undefined"></i>
  前端
</a></li><li class="dropdown-item"><!----> <a href="/categories/软件/" class="nav-link"><i class="undefined"></i>
  软件
</a></li><li class="dropdown-item"><!----> <a href="/categories/文档/" class="nav-link"><i class="undefined"></i>
  文档
</a></li><li class="dropdown-item"><!----> <a href="/categories/数据库/" class="nav-link"><i class="undefined"></i>
  数据库
</a></li><li class="dropdown-item"><!----> <a href="/categories/Node.js/" class="nav-link"><i class="undefined"></i>
  Node.js
</a></li><li class="dropdown-item"><!----> <a href="/categories/游戏/" class="nav-link"><i class="undefined"></i>
  游戏
</a></li></ul></div></div><div class="nav-item"><a href="/tag/" class="nav-link"><i class="iconfont reco-tag"></i>
  标签
</a></div><div class="nav-item"><a href="/timeline/" class="nav-link"><i class="iconfont reco-date"></i>
  时间线
</a></div><div class="nav-item"><div class="dropdown-wrapper"><a class="dropdown-title"><span class="title"><i class="iconfont reco-message"></i>
      文档
    </span> <span class="arrow right"></span></a> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/docs/minimal-boot/" class="nav-link"><i class="undefined"></i>
  minimal-boot
</a></li><li class="dropdown-item"><!----> <a href="/docs/minimal-cloud/" class="nav-link"><i class="undefined"></i>
  minimal-cloud
</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><a class="dropdown-title"><span class="title"><i class="iconfont reco-message"></i>
      联系方式
    </span> <span class="arrow right"></span></a> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="https://github.com/lzpeng723" target="_blank" rel="noopener noreferrer" class="nav-link external"><i class="iconfont reco-github"></i>
  GitHub
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li><li class="dropdown-item"><!----> <a href="https://gitee.com/lzpeng723" target="_blank" rel="noopener noreferrer" class="nav-link external"><i class="iconfont reco-mayun"></i>
  Gitee
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li><li class="dropdown-item"><!----> <a href="https://www.zhihu.com/people/lzpeng723" target="_blank" rel="noopener noreferrer" class="nav-link external"><i class="iconfont reco-zhihu"></i>
  知乎
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li></ul></div></div> <!----></nav></div></header> <div class="sidebar-mask" data-v-1156296a></div> <aside class="sidebar" data-v-1156296a><div class="personal-info-wrapper" data-v-828910c6 data-v-1156296a><img src="/avatar.jpg" alt="author-avatar" class="personal-img" data-v-828910c6> <h3 class="name" data-v-828910c6>
    lzpeng723
  </h3> <div class="num" data-v-828910c6><div data-v-828910c6><h3 data-v-828910c6>66</h3> <h6 data-v-828910c6>Articles</h6></div> <div data-v-828910c6><h3 data-v-828910c6>19</h3> <h6 data-v-828910c6>Tags</h6></div></div> <ul class="social-links" data-v-828910c6></ul> <hr data-v-828910c6></div> <nav class="nav-links"><div class="nav-item"><a href="/" class="nav-link"><i class="iconfont reco-home"></i>
  首页
</a></div><div class="nav-item"><div class="dropdown-wrapper"><a class="dropdown-title"><span class="title"><i class="iconfont reco-category"></i>
      分类
    </span> <span class="arrow right"></span></a> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/categories/运维/" class="nav-link"><i class="undefined"></i>
  运维
</a></li><li class="dropdown-item"><!----> <a href="/categories/开发/" class="nav-link"><i class="undefined"></i>
  开发
</a></li><li class="dropdown-item"><!----> <a href="/categories/后端/" class="nav-link"><i class="undefined"></i>
  后端
</a></li><li class="dropdown-item"><!----> <a href="/categories/前端/" class="nav-link"><i class="undefined"></i>
  前端
</a></li><li class="dropdown-item"><!----> <a href="/categories/软件/" class="nav-link"><i class="undefined"></i>
  软件
</a></li><li class="dropdown-item"><!----> <a href="/categories/文档/" class="nav-link"><i class="undefined"></i>
  文档
</a></li><li class="dropdown-item"><!----> <a href="/categories/数据库/" class="nav-link"><i class="undefined"></i>
  数据库
</a></li><li class="dropdown-item"><!----> <a href="/categories/Node.js/" class="nav-link"><i class="undefined"></i>
  Node.js
</a></li><li class="dropdown-item"><!----> <a href="/categories/游戏/" class="nav-link"><i class="undefined"></i>
  游戏
</a></li></ul></div></div><div class="nav-item"><a href="/tag/" class="nav-link"><i class="iconfont reco-tag"></i>
  标签
</a></div><div class="nav-item"><a href="/timeline/" class="nav-link"><i class="iconfont reco-date"></i>
  时间线
</a></div><div class="nav-item"><div class="dropdown-wrapper"><a class="dropdown-title"><span class="title"><i class="iconfont reco-message"></i>
      文档
    </span> <span class="arrow right"></span></a> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/docs/minimal-boot/" class="nav-link"><i class="undefined"></i>
  minimal-boot
</a></li><li class="dropdown-item"><!----> <a href="/docs/minimal-cloud/" class="nav-link"><i class="undefined"></i>
  minimal-cloud
</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><a class="dropdown-title"><span class="title"><i class="iconfont reco-message"></i>
      联系方式
    </span> <span class="arrow right"></span></a> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="https://github.com/lzpeng723" target="_blank" rel="noopener noreferrer" class="nav-link external"><i class="iconfont reco-github"></i>
  GitHub
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li><li class="dropdown-item"><!----> <a href="https://gitee.com/lzpeng723" target="_blank" rel="noopener noreferrer" class="nav-link external"><i class="iconfont reco-mayun"></i>
  Gitee
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li><li class="dropdown-item"><!----> <a href="https://www.zhihu.com/people/lzpeng723" target="_blank" rel="noopener noreferrer" class="nav-link external"><i class="iconfont reco-zhihu"></i>
  知乎
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li></ul></div></div> <!----></nav> <!----> </aside> <div class="password-shadow password-wrapper-in" style="display:none;" data-v-4e82dffc data-v-1156296a><h3 class="title" data-v-4e82dffc data-v-4e82dffc>Java Agent 使用指南</h3> <!----> <label id="box" class="inputBox" data-v-4e82dffc data-v-4e82dffc><input type="password" value="" data-v-4e82dffc> <span data-v-4e82dffc>Konck! Knock!</span> <button data-v-4e82dffc>OK</button></label> <div class="footer" data-v-4e82dffc data-v-4e82dffc><span data-v-4e82dffc><i class="iconfont reco-theme" data-v-4e82dffc></i> <a target="blank" href="https://vuepress-theme-reco.recoluan.com" data-v-4e82dffc>vuePress-theme-reco</a></span> <span data-v-4e82dffc><i class="iconfont reco-copyright" data-v-4e82dffc></i> <a data-v-4e82dffc><span data-v-4e82dffc>lzpeng723</span>
            
          <span data-v-4e82dffc>2021 - </span>
          2022
        </a></span></div></div> <div data-v-1156296a><main class="page"><section><div class="page-title"><h1 class="title">Java Agent 使用指南</h1> <div data-v-1ff7123e><i class="iconfont reco-account" data-v-1ff7123e><span data-v-1ff7123e>lzpeng723</span></i> <i class="iconfont reco-date" data-v-1ff7123e><span data-v-1ff7123e>12/19/2021</span></i> <!----> <i class="tags iconfont reco-tag" data-v-1ff7123e><span class="tag-item" data-v-1ff7123e>Java</span></i></div></div> <div class="theme-reco-content content__default"><h2 id="介绍"><a href="#介绍" class="header-anchor">#</a> 介绍</h2> <p>使用 Instrumentation，<strong>使得开发者可以构建一个独立于应用程序的代理程序（Agent），用来监测和协助运行在 JVM 上的程序，甚至能够替换和修改某些类的定义</strong>。有了这样的功能，开发者就可以实现更为灵活的运行时虚拟机监控和 Java 类操作了，这样的特性实际上提供了 <strong>一种虚拟机级别支持的 AOP 实现方式</strong>，使得开发者无需对 JDK 做任何升级和改动，就可以实现某些 AOP 的功能了。</p> <p>利用 java.lang.instrument 做静态 Instrumentation 是 Java SE 5 的新特性，<strong>它把 Java 的 instrument 功能从本地代码中解放出来，使之可以用 Java 代码的方式解决问题</strong>。</p> <p>在 Java SE 6 里面，instrumentation 包被赋予了更强大的功能：<strong>启动后的 instrument、本地代码（native code）instrument，以及动态改变 classpath</strong> 等等。这些改变，意味着 <strong>Java 具有了更强的动态控制、解释能力，它使得 Java 语言变得更加灵活多变</strong>。</p> <blockquote><ol><li><strong>在 Java SE6 里面，最大的改变使运行时的 Instrumentation 成为可能</strong>。在 Java SE 5 中，<strong>Instrument 要求在运行前利用命令行参数或者系统参数来设置代理类</strong>，在实际的运行之中，<strong>虚拟机在初始化之时（在绝大多数的 Java 类库被载入之前），instrumentation 的设置已经启动，并在虚拟机中设置了回调函数，检测特定类的加载情况，并完成实际工作。但是在实际的很多的情况下，我们没有办法在虚拟机启动之时就为其设定代理，这样实际上限制了 instrument 的应用</strong>。而 Java SE 6 的新特性改变了这种情况，<strong>通过 Java Tool API 中的 attach 方式</strong>，我们可以很方便地 <strong>在运行过程中动态地设置加载代理类，以达到 instrumentation 的目的</strong>。</li> <li>另外，对 native 的 Instrumentation 也是 Java SE 6 的一个崭新的功能，这使以前无法完成的功能 —— <strong>对 native 接口的 instrumentation 可以在 Java SE 6 中，通过一个或者一系列的 prefix 添加而得以完成</strong>。</li> <li>最后，<strong>Java SE 6 里的 Instrumentation 也增加了动态添加 class path的功能</strong>。所有这些新的功能，都使得 instrument 包的功能更加丰富，从而使 Java 语言本身更加强大。</li></ol></blockquote> <h2 id="基本功能和用法"><a href="#基本功能和用法" class="header-anchor">#</a> 基本功能和用法</h2> <p>事实上，java.lang.instrument 包的实现，是基于JVMTI机制的：<strong>在 Instrumentation 的实现当中，存在一个 JVMTI 的代理程序，通过调用 JVMTI 当中 Java 类相关的函数来完成Java 类的动态操作</strong>。除开 Instrumentation 功能外，<strong>JVMTI 还在虚拟机内存管理，线程控制，方法和变量操作等方面提供了大量有价值的函数</strong>。</p> <blockquote><p>JVMTI（Java Virtual Machine Tool Interface）是一套由 Java 虚拟机提供的，为 JVM 相关的工具提供的本地编程接口集合。JVMTI 是从 Java SE 5 开始引入，整合和取代了以前使用的 Java Virtual Machine Profiler Interface (JVMPI) 和 the Java Virtual Machine Debug Interface (JVMDI)，而在 Java SE 6 中，JVMPI 和 JVMDI 已经消失了。<strong>JVMTI 提供了一套”代理”程序机制，可以支持第三方工具程序以代理的方式连接和访问 JVM，并利用 JVMTI 提供的丰富的编程接口，完成很多跟 JVM 相关的功能</strong>。</p></blockquote> <h2 id="instrument两个核心api"><a href="#instrument两个核心api" class="header-anchor">#</a> Instrument两个核心API</h2> <p><img src="/assets/img/javaagent1.7d50fc53.png" alt="javaagent1"></p> <ol><li><p><strong>ClassFileTransformer</strong>：<strong>定义了类加载前的预处理类</strong>，可以在这个类中对要加载的类的字节码做一些处理，譬如进行字节码增强；</p></li> <li><p><strong>Instrumentation</strong>：增强器，由JVM在入口参数中传递给我们，提供了如下的功能：</p> <div class="language-java line-numbers-mode"><pre class="language-java"><code><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">Instrumentation</span> <span class="token punctuation">{</span>
    
    <span class="token comment">//增加一个Class 文件的转换器，转换器用于改变 Class 二进制流的数据，参数 canRetransform 设置是否允许重新转换。</span>
    <span class="token keyword">void</span> <span class="token function">addTransformer</span><span class="token punctuation">(</span><span class="token class-name">ClassFileTransformer</span> transformer<span class="token punctuation">,</span> <span class="token keyword">boolean</span> canRetransform<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">//在类加载之前，重新定义 Class 文件，ClassDefinition 表示对一个类新的定义，如果在类加载之后，需要使用 retransformClasses 方法重新定义。addTransformer方法配置之后，后续的类加载都会被Transformer拦截。对于已经加载过的类，可以执行retransformClasses来重新触发这个Transformer的拦截。类加载的字节码被修改后，除非再次被retransform，否则不会恢复。</span>
    <span class="token keyword">void</span> <span class="token function">addTransformer</span><span class="token punctuation">(</span><span class="token class-name">ClassFileTransformer</span> transformer<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">//删除一个类转换器</span>
    <span class="token keyword">boolean</span> <span class="token function">removeTransformer</span><span class="token punctuation">(</span><span class="token class-name">ClassFileTransformer</span> transformer<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">//是否支持重新转换类</span>
    <span class="token keyword">boolean</span> <span class="token function">isRetransformClassesSupported</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">//在类加载之后，重新定义 Class。这个很重要，该方法是1.6 之后加入的，事实上，该方法是 update 了一个类。</span>
    <span class="token keyword">void</span> <span class="token function">retransformClasses</span><span class="token punctuation">(</span><span class="token class-name">Class</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token operator">?</span><span class="token punctuation">&gt;</span></span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> classes<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">UnmodifiableClassException</span><span class="token punctuation">;</span>

    <span class="token comment">//是否支持重新定义类</span>
    <span class="token keyword">boolean</span> <span class="token function">isRedefineClassesSupported</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">//重新定义类</span>
    <span class="token keyword">void</span> <span class="token function">redefineClasses</span><span class="token punctuation">(</span><span class="token class-name">ClassDefinition</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> definitions<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">ClassNotFoundException</span><span class="token punctuation">,</span> <span class="token class-name">UnmodifiableClassException</span><span class="token punctuation">;</span>
    
    <span class="token comment">//theClass是否已被修改</span>
    <span class="token keyword">boolean</span> <span class="token function">isModifiableClass</span><span class="token punctuation">(</span><span class="token class-name">Class</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token operator">?</span><span class="token punctuation">&gt;</span></span> theClass<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">//获取当前JVM已加载的所有类</span>
    <span class="token annotation punctuation">@SuppressWarnings</span><span class="token punctuation">(</span><span class="token string">&quot;rawtypes&quot;</span><span class="token punctuation">)</span>
    <span class="token class-name">Class</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">getAllLoadedClasses</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">//获取当前JVM已初始化的所有类</span>
    <span class="token annotation punctuation">@SuppressWarnings</span><span class="token punctuation">(</span><span class="token string">&quot;rawtypes&quot;</span><span class="token punctuation">)</span>
    <span class="token class-name">Class</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">getInitiatedClasses</span><span class="token punctuation">(</span><span class="token class-name">ClassLoader</span> loader<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">//获取一个对象的大小</span>
    <span class="token keyword">long</span> <span class="token function">getObjectSize</span><span class="token punctuation">(</span><span class="token class-name">Object</span> objectToSize<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">//将jarfile添加至系统类加载器</span>
    <span class="token keyword">void</span> <span class="token function">appendToBootstrapClassLoaderSearch</span><span class="token punctuation">(</span><span class="token class-name">JarFile</span> jarfile<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">//将jarfile添加至系统类加载器</span>
    <span class="token keyword">void</span> <span class="token function">appendToSystemClassLoaderSearch</span><span class="token punctuation">(</span><span class="token class-name">JarFile</span> jarfile<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">//是否支持修改本地方法前缀</span>
    <span class="token keyword">boolean</span> <span class="token function">isNativeMethodPrefixSupported</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">//设置修改本地方法前缀</span>
    <span class="token keyword">void</span> <span class="token function">setNativeMethodPrefix</span><span class="token punctuation">(</span><span class="token class-name">ClassFileTransformer</span> transformer<span class="token punctuation">,</span> <span class="token class-name">String</span> prefix<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br></div></div></li></ol> <h2 id="jvm启动前静态instrument"><a href="#jvm启动前静态instrument" class="header-anchor">#</a> JVM启动前静态Instrument</h2> <p><strong>Instrumentation 的最大作用，就是类定义动态改变和操作</strong>。在 Java SE 5 及其后续版本当中，开发者可以在一个普通 Java 程序（带有 main 函数的 Java 类）运行时，<strong>通过 -javaagent参数指定一个特定的 jar 文件（包含 Instrumentation 代理）来启动 Instrumentation 的代理程序</strong>。</p> <ol><li><p><strong>编写 premain 函数</strong></p> <p><strong>编写一个 Java 类，包含如下两个方法当中的任何一个</strong>：</p> <div class="language-java line-numbers-mode"><pre class="language-java"><code><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">premain</span><span class="token punctuation">(</span><span class="token class-name">String</span> agentArgs<span class="token punctuation">,</span> <span class="token class-name">Instrumentation</span> inst<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span>
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">premain</span><span class="token punctuation">(</span><span class="token class-name">String</span> agentArgs<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div></li></ol> <p>其中，<strong>[1] 的优先级比 [2] 高，将会被优先执行（[1] 和 [2] 同时存在时，[2] 被忽略）</strong>。在这个 premain 函数中，开发者可以进行对类的各种操作。
这个逻辑在<code>sun.instrument.InstrumentationImpl</code>类中：</p> <p><img src="/assets/img/javaagent2.cb0dee4f.png" alt="javaagent2"></p> <blockquote><ol><li><strong>agentArgs 是 premain 函数得到的程序参数</strong>，随同 “-javaagent”一起传入。与 main 函数不同的是，<strong>这个参数是一个字符串而不是一个字符串数组</strong>，如果程序参数有多个，程序将自行解析这个字符串。</li> <li><strong>inst 是一个 java.lang.instrument.Instrumentation 的实例，由 JVM 自动传入</strong>。java.lang.instrument.Instrumentation 是 instrument 包中定义的一个接口，也是这个包的核心部分，<strong>集中了其中几乎所有的功能方法</strong>，例如类定义的转换和操作等等。</li></ol></blockquote> <ol start="2"><li><p><strong>jar 文件打包</strong></p> <p>将这个 Java 类打包成一个 jar 文件，<strong>并在其中的META-INF/MAINIFEST.MF属性当中加入” Premain-Class”来指定步骤 1 当中编写的那个带有 premain 的 Java 类</strong>。（可能还需要指定其他属性以开启更多功能）</p></li> <li><p><strong>运行</strong></p> <p>用如下方式运行带有 Instrumentation 的 Java 程序：</p> <div class="language-bash line-numbers-mode"><pre class="language-bash"><code>java -javaagent:jar文件的位置 <span class="token punctuation">[</span><span class="token operator">=</span> 传入 premain 的参数 <span class="token punctuation">]</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div></li></ol> <p><strong>举个替换Java类文件栗子：采用简单的类文件替换的方式来演示 Instrumentation 的使用</strong></p> <p>对 Java 类文件的操作，可以理解为对一个 byte 数组的操作（将类文件的二进制字节流读入一个 byte 数组）。<strong>开发者可以在“ClassFileTransformer”的 transform 方法当中得到，操作并最终返回一个类的定义（一个 byte 数组）</strong>。</p> <ol><li><p><strong>首先，我们有一个简单的类，TransClass， 可以通过一个静态方法返回一个整数 1</strong></p> <div class="language-java line-numbers-mode"><pre class="language-java"><code><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">TransClass</span> <span class="token punctuation">{</span> 
    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">getNumber</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> 
       <span class="token keyword">return</span> <span class="token number">1</span><span class="token punctuation">;</span> 
    <span class="token punctuation">}</span> 
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div></li></ol> <p>我们运行如下类，可以得到输出 “1”：</p> <div class="language-java line-numbers-mode"><pre class="language-java"><code><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">TestMainInJar</span> <span class="token punctuation">{</span> 
  <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span> 
     <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">TransClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getNumber</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 
  <span class="token punctuation">}</span> 
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><ol start="2"><li><p><strong>然后，我们将 TransClass 的 getNumber 方法改成如下：</strong></p> <div class="language-java line-numbers-mode"><pre class="language-java"><code><span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">getNumber</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> 
    <span class="token keyword">return</span> <span class="token number">2</span><span class="token punctuation">;</span> 
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div></li></ol> <p>再将这个返回 2 的 Java 文件编译成类文件，为了区别开原有的返回 1 的类，<strong>我们将返回 2 的这个类文件命名为 TransClass2.class.2</strong>。</p> <ol start="3"><li><p><strong>接下来，我们建立一个 Transformer 类：这个类实现了 ClassFileTransformer 接口</strong></p> <div class="language-java line-numbers-mode"><pre class="language-java"><code><span class="token keyword">import</span> <span class="token namespace">java<span class="token punctuation">.</span>io<span class="token punctuation">.</span></span><span class="token class-name">File</span><span class="token punctuation">;</span> 
<span class="token keyword">import</span> <span class="token namespace">java<span class="token punctuation">.</span>io<span class="token punctuation">.</span></span><span class="token class-name">FileInputStream</span><span class="token punctuation">;</span> 
<span class="token keyword">import</span> <span class="token namespace">java<span class="token punctuation">.</span>io<span class="token punctuation">.</span></span><span class="token class-name">IOException</span><span class="token punctuation">;</span> 
<span class="token keyword">import</span> <span class="token namespace">java<span class="token punctuation">.</span>io<span class="token punctuation">.</span></span><span class="token class-name">InputStream</span><span class="token punctuation">;</span> 
<span class="token keyword">import</span> <span class="token namespace">java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>instrument<span class="token punctuation">.</span></span><span class="token class-name">ClassFileTransformer</span><span class="token punctuation">;</span> 
<span class="token keyword">import</span> <span class="token namespace">java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>instrument<span class="token punctuation">.</span></span><span class="token class-name">IllegalClassFormatException</span><span class="token punctuation">;</span> 
<span class="token keyword">import</span> <span class="token namespace">java<span class="token punctuation">.</span>security<span class="token punctuation">.</span></span><span class="token class-name">ProtectionDomain</span><span class="token punctuation">;</span> 

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Transformer</span> <span class="token keyword">implements</span> <span class="token class-name">ClassFileTransformer</span> <span class="token punctuation">{</span> 

    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> classNumberReturns2 <span class="token operator">=</span> <span class="token string">&quot;TransClass.class.2&quot;</span><span class="token punctuation">;</span> 

    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">getBytesFromFile</span><span class="token punctuation">(</span><span class="token class-name">String</span> fileName<span class="token punctuation">)</span> <span class="token punctuation">{</span> 
        <span class="token keyword">try</span> <span class="token punctuation">{</span> 
            <span class="token comment">// precondition </span>
            <span class="token class-name">File</span> file <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">File</span><span class="token punctuation">(</span>fileName<span class="token punctuation">)</span><span class="token punctuation">;</span> 
            <span class="token class-name">InputStream</span> is <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FileInputStream</span><span class="token punctuation">(</span>file<span class="token punctuation">)</span><span class="token punctuation">;</span> 
            <span class="token keyword">long</span> length <span class="token operator">=</span> file<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 
            <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> bytes <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span> length<span class="token punctuation">]</span><span class="token punctuation">;</span> 

            <span class="token comment">// Read in the bytes </span>
            <span class="token keyword">int</span> offset <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> 
            <span class="token keyword">int</span> numRead <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> 
            <span class="token keyword">while</span> <span class="token punctuation">(</span>offset <span class="token operator">&lt;</span>bytes<span class="token punctuation">.</span>length 
                 <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>numRead <span class="token operator">=</span> is<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>bytes<span class="token punctuation">,</span> offset<span class="token punctuation">,</span> bytes<span class="token punctuation">.</span>length <span class="token operator">-</span> offset<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">&gt;=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> 
                offset <span class="token operator">+=</span> numRead<span class="token punctuation">;</span> 
            <span class="token punctuation">}</span> 

            <span class="token keyword">if</span> <span class="token punctuation">(</span>offset <span class="token operator">&lt;</span> bytes<span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token punctuation">{</span> 
                <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IOException</span><span class="token punctuation">(</span><span class="token string">&quot;Could not completely read file &quot;</span>
                     <span class="token operator">+</span> file<span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 
            <span class="token punctuation">}</span> 
            is<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 
            <span class="token keyword">return</span> bytes<span class="token punctuation">;</span> 
        <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> 
            <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&quot;error occurs in _ClassTransformer!&quot;</span>
                 <span class="token operator">+</span> e<span class="token punctuation">.</span><span class="token function">getClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 
            <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span> 
        <span class="token punctuation">}</span> 
    <span class="token punctuation">}</span> 
   <span class="token comment">/**
    * 参数： 
    * loader - 定义要转换的类加载器；如果是引导加载器，则为 null 
    * className - 完全限定类内部形式的类名称和 The Java Virtual Machine Specification 中定义的接口名称。例如，&quot;java/util/List&quot;。 
    * classBeingRedefined - 如果是被重定义或重转换触发，则为重定义或重转换的类；如果是类加载，则为 null 
    * protectionDomain - 要定义或重定义的类的保护域 
    * classfileBuffer - 类文件格式的输入字节缓冲区（不得修改） 
    * 返回： 
    * 一个格式良好的类文件缓冲区（转换的结果），如果未执行转换,则返回 null。 
    * 抛出： 
    * IllegalClassFormatException - 如果输入不表示一个格式良好的类文件 
    */</span>
    <span class="token keyword">public</span> <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">transform</span><span class="token punctuation">(</span><span class="token class-name">ClassLoader</span> l<span class="token punctuation">,</span> <span class="token class-name">String</span> className<span class="token punctuation">,</span> <span class="token class-name">Class</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token operator">?</span><span class="token punctuation">&gt;</span></span> c<span class="token punctuation">,</span> 
         <span class="token class-name">ProtectionDomain</span> pd<span class="token punctuation">,</span> <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> b<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">IllegalClassFormatException</span> <span class="token punctuation">{</span> 
        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>className<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span><span class="token string">&quot;TransClass&quot;</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> 
            <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span> 
        <span class="token punctuation">}</span> 
        <span class="token keyword">return</span> <span class="token function">getBytesFromFile</span><span class="token punctuation">(</span>classNumberReturns2<span class="token punctuation">)</span><span class="token punctuation">;</span> 
    <span class="token punctuation">}</span> 
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br><span class="line-number">57</span><br><span class="line-number">58</span><br><span class="line-number">59</span><br><span class="line-number">60</span><br></div></div></li></ol> <p><strong>其中，getBytesFromFile 方法根据文件名读入二进制字符流，而 ClassFileTransformer 当中规定的 transform 方法则完成了类定义的替换转换</strong>。</p> <ol start="4"><li><p><strong>最后，我们建立一个 Premain 类，写入 Instrumentation 的代理方法 premain：</strong></p> <div class="language-java line-numbers-mode"><pre class="language-java"><code><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Premain</span> <span class="token punctuation">{</span> 
    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">premain</span><span class="token punctuation">(</span><span class="token class-name">String</span> agentArgs， <span class="token class-name">Instrumentation</span> inst<span class="token punctuation">)</span>  <span class="token keyword">throws</span> <span class="token class-name">ClassNotFoundException</span>， <span class="token class-name">UnmodifiableClassException</span> <span class="token punctuation">{</span> 
        inst<span class="token punctuation">.</span><span class="token function">addTransformer</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Transformer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 
    <span class="token punctuation">}</span> 
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div></li></ol> <p>可以看出，addTransformer 方法并没有指明要转换哪个类。<strong>转换发生在 premain 函数执行之后，main 函数执行之前，这时每装载一个类，transform 方法就会执行一次，看看是否需要转换</strong>，所以，在 transform（Transformer 类中）方法中，程序用 className.equals(&quot;TransClass&quot;) 来判断当前的类是否需要转换。</p> <ol start="5"><li><p><strong>代码完成后，我们将他们打包为 TestInstrument1.jar。返回 1 的那个 TransClass 的类文件保留在 jar 包中，而返回 2 的那个 TransClass.class.2 则放到 jar 的外面。在 manifest 里面加入如下属性来指定 premain 所在的类：</strong></p> <div class="language-txt line-numbers-mode"><pre class="language-txt"><code>Manifest-Version: 1.0 
Premain-Class: Premain
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div></li> <li><p><strong>在运行这个程序的时候，如果我们用普通方式运行这个 jar 中的 main 函数，可以得到输出“1”。如果用下列方式运行：</strong></p> <div class="language-bash line-numbers-mode"><pre class="language-bash"><code>java -javaagent:TestInstrument1.jar -cp TestInstrument1.jar TestMainInJar
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div></li></ol> <p>则会得到输出“2”。</p> <p>当然，<strong>程序运行的 main 函数不一定要放在 premain 所在的这个 jar 文件里面</strong>，这里只是为了例子程序打包的方便而放在一起的。 <strong>除开用 addTransformer 的方式，Instrumentation 当中还有另外一个方法“redefineClasses”来实现 premain 当中指定的转换</strong>。用法类似，如下：</p> <div class="language-java line-numbers-mode"><pre class="language-java"><code><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Premain</span> <span class="token punctuation">{</span> 
    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">premain</span><span class="token punctuation">(</span><span class="token class-name">String</span> agentArgs， <span class="token class-name">Instrumentation</span> inst<span class="token punctuation">)</span>  <span class="token keyword">throws</span> <span class="token class-name">ClassNotFoundException</span>， <span class="token class-name">UnmodifiableClassException</span> <span class="token punctuation">{</span> 
        <span class="token class-name">ClassDefinition</span> def <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ClassDefinition</span><span class="token punctuation">(</span><span class="token class-name">TransClass</span><span class="token punctuation">.</span><span class="token keyword">class</span>， <span class="token class-name">Transformer</span><span class="token punctuation">.</span><span class="token function">getBytesFromFile</span><span class="token punctuation">(</span><span class="token class-name">Transformer</span><span class="token punctuation">.</span>classNumberReturns2<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 
        inst<span class="token punctuation">.</span><span class="token function">redefineClasses</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ClassDefinition</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> def <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&quot;success&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 
    <span class="token punctuation">}</span> 
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div><p><strong>redefineClasses 的功能比较强大，可以批量转换很多类。</strong></p> <p><img src="/assets/img/javaagent7.70107877.png" alt="javaagent7"></p> <p>多个代理可以同时执行，按照代理指定的顺序被依次调用</p> <h2 id="jvm启动后动态instrument"><a href="#jvm启动后动态instrument" class="header-anchor">#</a> JVM启动后动态Instrument</h2> <p>在 Java SE 5 当中，开发者只能在 premain 当中施展想象力，<strong>所作的 Instrumentation 也仅限与 main 函数执行前，这样的方式存在一定的局限性</strong>。</p> <p>在 Java SE 5 的基础上，Java SE 6 针对这种状况做出了改进，<strong>开发者可以在 main 函数开始执行以后，再启动自己的 Instrumentation 程序</strong>。</p> <p>在 Java SE 6 的 Instrumentation 当中，<strong>有一个跟 premain“并驾齐驱”的“agentmain”方法，可以在 main 函数开始运行之后再运行</strong>。跟 premain 函数一样， 开发者可以编写一个含有“agentmain”函数的 Java 类：</p> <div class="language-java line-numbers-mode"><pre class="language-java"><code><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> agentmain <span class="token punctuation">(</span><span class="token class-name">String</span> agentArgs<span class="token punctuation">,</span> <span class="token class-name">Instrumentation</span> inst<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> 
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> agentmain <span class="token punctuation">(</span><span class="token class-name">String</span> agentArgs<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><p>同样，<strong>[1] 的优先级比 [2] 高，将会被优先执行（[1] 和 [2] 同时存在时，[2] 被忽略）</strong>。跟 premain 函数一样，开发者可以在 agentmain 中进行对类的各种操作。其中的 agentArgs 和 Inst 的用法跟 premain 相同。</p> <blockquote><p>与“Premain-Class”类似，开发者必须在 manifest 文件里面设置“Agent-Class”来指定包含 agentmain 函数的类。</p></blockquote> <p>可是，跟 premain 不同的是，agentmain 需要在 main 函数开始运行后才启动，<strong>这样的时机应该如何确定呢，这样的功能又如何实现呢？</strong></p> <p>在 Java SE 6 文档当中，开发者也许无法在 java.lang.instrument 包相关的文档部分看到明确的介绍，更加无法看到具体的应用 agnetmain 的例子。不过，在 Java SE 6 的新特性里面，有一个不太起眼的地方，揭示了 agentmain 的用法。<strong>这就是 Java SE 6 当中提供的 Attach API</strong>。</p> <p>Attach API 不是 Java 的标准 API，而是 Sun 公司提供的一套扩展 API，<strong>用来向目标 JVM ”附着”（Attach）代理工具程序的</strong>。有了它，<strong>开发者可以方便的监控一个 JVM，运行一个外加的代理程序</strong>。</p> <p>Attach API 很简单，只有 2 个主要的类，都在 com.sun.tools.attach 包里面：</p> <blockquote><ol><li><p><strong>VirtualMachine 代表一个 Java 虚拟机</strong>，也就是程序需要监控的目标虚拟机，提供了 JVM 枚举，<strong>Attach 动作和 Detach 动作（Attach 动作的相反行为，从 JVM 上面解除一个代理）等等</strong> ;</p> <p>VirtualMachine类，该类允许我们 <strong>通过给attach方法传入一个jvm的pid（进程id），远程连接到jvm上</strong> 。然后我们可以 <strong>通过loadAgent方法向jvm注册一个代理程序agent，在该agent的代理程序中会得到一个Instrumentation实例</strong>，该实例可以 <strong>在class加载前改变class的字节码，也可以在class加载后重新加载</strong>。在调用Instrumentation实例的方法时，这些方法会使用ClassFileTransformer接口中提供的方法进行处理。</p></li> <li><p><strong>VirtualMachineDescriptor 则是一个描述虚拟机的容器类</strong>，配合 VirtualMachine 类完成各种功能。</p></li></ol></blockquote> <p><strong>为了简单起见，我们举例简化如下</strong>：依然用类文件替换的方式</p> <blockquote><ol><li>将一个返回 1 的函数替换成返回 2 的函数，Attach API 写在一个线程里面，用睡眠等待的方式，<strong>每隔半秒时间检查一次所有的 Java 虚拟机</strong>，当发现有新的虚拟机出现的时候，<strong>就调用 attach 函数，随后再按照 Attach API 文档里面所说的方式装载 Jar 文件</strong>。等到 5 秒钟的时候，attach 程序自动结束。</li> <li>在 main 函数里面，程序每隔半秒钟输出一次返回值（显示出返回值从 1 变成 2）。</li></ol></blockquote> <p><strong>TransClass 类和 Transformer 类的代码不变，参看上一节介绍。 含有 main 函数的 TestMainInJar 代码为：</strong></p> <div class="language-java line-numbers-mode"><pre class="language-java"><code><span class="token comment">// 程序每隔半秒钟输出一次返回值（显示出返回值从 1 变成 2）</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">TestMainInJar</span> <span class="token punctuation">{</span> 
    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">InterruptedException</span> <span class="token punctuation">{</span> 
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">TransClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getNumber</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 
        <span class="token keyword">int</span> count <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> 
        <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> 
            <span class="token class-name">Thread</span><span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">500</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 
            count<span class="token operator">++</span><span class="token punctuation">;</span> 
            <span class="token keyword">int</span> number <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TransClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getNumber</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 
            <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>number<span class="token punctuation">)</span><span class="token punctuation">;</span> 
            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token number">3</span> <span class="token operator">==</span> number <span class="token operator">||</span> count <span class="token operator">&gt;=</span> <span class="token number">10</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> 
                <span class="token keyword">break</span><span class="token punctuation">;</span> 
            <span class="token punctuation">}</span> 
        <span class="token punctuation">}</span> 
    <span class="token punctuation">}</span> 
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br></div></div><p><strong>含有 agentmain 的 AgentMain 类的代码为：</strong></p> <div class="language-java line-numbers-mode"><pre class="language-java"><code><span class="token keyword">import</span> <span class="token namespace">java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>instrument<span class="token punctuation">.</span></span><span class="token class-name">ClassDefinition</span><span class="token punctuation">;</span> 
<span class="token keyword">import</span> <span class="token namespace">java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>instrument<span class="token punctuation">.</span></span><span class="token class-name">Instrumentation</span><span class="token punctuation">;</span> 
<span class="token keyword">import</span> <span class="token namespace">java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>instrument<span class="token punctuation">.</span></span><span class="token class-name">UnmodifiableClassException</span><span class="token punctuation">;</span> 

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">AgentMain</span> <span class="token punctuation">{</span> 
    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">agentmain</span><span class="token punctuation">(</span><span class="token class-name">String</span> agentArgs<span class="token punctuation">,</span> <span class="token class-name">Instrumentation</span> inst<span class="token punctuation">)</span>  <span class="token keyword">throws</span> <span class="token class-name">ClassNotFoundException</span><span class="token punctuation">,</span> <span class="token class-name">UnmodifiableClassException</span><span class="token punctuation">,</span> <span class="token class-name">InterruptedException</span> <span class="token punctuation">{</span> 
        inst<span class="token punctuation">.</span><span class="token function">addTransformer</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Transformer</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 
        inst<span class="token punctuation">.</span><span class="token function">retransformClasses</span><span class="token punctuation">(</span><span class="token class-name">TransClass</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&quot;Agent Main Done&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 
    <span class="token punctuation">}</span> 
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br></div></div><p>其中，<strong>retransformClasses 是 Java SE 6 里面的新方法，它跟 redefineClasses 一样，可以批量转换类定义，多用于 agentmain 场合</strong>。</p> <p>Jar 文件跟 Premain 那个例子里面的 Jar 文件差不多，也是把 main 和 agentmain 的类，TransClass，Transformer 等类放在一起，打包为“TestInstrument1.jar”，而 Jar 文件当中的 Manifest 文件为 :</p> <div class="language-txt line-numbers-mode"><pre class="language-txt"><code>Manifest-Version: 1.0 
Agent-Class: AgentMain
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><p>另外，为了运行 Attach API，我们可以再写一个控制程序来模拟监控过程：</p> <div class="language-java line-numbers-mode"><pre class="language-java"><code><span class="token keyword">import</span> <span class="token namespace">com<span class="token punctuation">.</span>sun<span class="token punctuation">.</span>tools<span class="token punctuation">.</span>attach<span class="token punctuation">.</span></span><span class="token class-name">VirtualMachine</span><span class="token punctuation">;</span> 
<span class="token keyword">import</span> <span class="token namespace">com<span class="token punctuation">.</span>sun<span class="token punctuation">.</span>tools<span class="token punctuation">.</span>attach<span class="token punctuation">.</span></span><span class="token class-name">VirtualMachineDescriptor</span><span class="token punctuation">;</span> 

<span class="token comment">// 一个运行 Attach API 的线程子类</span>
<span class="token comment">// 每隔半秒时间检查一次所有的 Java 虚拟机</span>
<span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">AttachThread</span> <span class="token keyword">extends</span> <span class="token class-name">Thread</span> <span class="token punctuation">{</span> 
        
        <span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">VirtualMachineDescriptor</span><span class="token punctuation">&gt;</span></span> listBefore<span class="token punctuation">;</span> 

        <span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">String</span> jar<span class="token punctuation">;</span> 

        <span class="token class-name">AttachThread</span><span class="token punctuation">(</span><span class="token class-name">String</span> attachJar<span class="token punctuation">,</span> <span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">VirtualMachineDescriptor</span><span class="token punctuation">&gt;</span></span> vms<span class="token punctuation">)</span> <span class="token punctuation">{</span> 
            listBefore <span class="token operator">=</span> vms<span class="token punctuation">;</span>  <span class="token comment">// 记录程序启动时的 VM 集合</span>
            jar <span class="token operator">=</span> attachJar<span class="token punctuation">;</span> 
        <span class="token punctuation">}</span> 

        <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> 
            <span class="token class-name">VirtualMachine</span> vm <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span> 
            <span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">VirtualMachineDescriptor</span><span class="token punctuation">&gt;</span></span> listAfter <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span> 
            <span class="token keyword">try</span> <span class="token punctuation">{</span> 
                <span class="token keyword">int</span> count <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> 
                <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> 
                    listAfter <span class="token operator">=</span> <span class="token class-name">VirtualMachine</span><span class="token punctuation">.</span><span class="token function">list</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 
                    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token class-name">VirtualMachineDescriptor</span> vmd <span class="token operator">:</span> listAfter<span class="token punctuation">)</span> <span class="token punctuation">{</span> 
                        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>listBefore<span class="token punctuation">.</span><span class="token function">contains</span><span class="token punctuation">(</span>vmd<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> 
                            <span class="token comment">// 如果 VM 有增加，我们就认为是被监控的 VM 启动了</span>
                            <span class="token comment">// 这时，我们开始监控这个 VM </span>
                            vm <span class="token operator">=</span> <span class="token class-name">VirtualMachine</span><span class="token punctuation">.</span><span class="token function">attach</span><span class="token punctuation">(</span>vmd<span class="token punctuation">)</span><span class="token punctuation">;</span> 
                            <span class="token keyword">break</span><span class="token punctuation">;</span> 
                        <span class="token punctuation">}</span> 
                    <span class="token punctuation">}</span> 
                    <span class="token class-name">Thread</span><span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">500</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 
                    count<span class="token operator">++</span><span class="token punctuation">;</span> 
                    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">null</span> <span class="token operator">!=</span> vm <span class="token operator">||</span> count <span class="token operator">&gt;=</span> <span class="token number">10</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> 
                        <span class="token keyword">break</span><span class="token punctuation">;</span> 
                    <span class="token punctuation">}</span> 
                <span class="token punctuation">}</span> 
                vm<span class="token punctuation">.</span><span class="token function">loadAgent</span><span class="token punctuation">(</span>jar<span class="token punctuation">)</span><span class="token punctuation">;</span> 
                vm<span class="token punctuation">.</span><span class="token function">detach</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 
            <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> 
                 <span class="token comment">// ignore </span>
            <span class="token punctuation">}</span> 
        <span class="token punctuation">}</span> 
    <span class="token punctuation">}</span> 

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">InterruptedException</span> <span class="token punctuation">{</span>     
     <span class="token keyword">new</span> <span class="token class-name">AttachThread</span><span class="token punctuation">(</span><span class="token string">&quot;TestInstrument1.jar&quot;</span><span class="token punctuation">,</span> <span class="token class-name">VirtualMachine</span><span class="token punctuation">.</span><span class="token function">list</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br></div></div><p>运行时，<strong>首先运行上面这个启动新线程的 main 函数</strong>，然后，在 5 秒钟内（仅仅简单模拟 JVM 的监控过程）运行如下命令启动测试 Jar 文件 :</p> <div class="language-bash line-numbers-mode"><pre class="language-bash"><code>java –cp TestInstrument1.jar TestMainInJar
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>如果时间掌握得不太差的话，程序首先会在屏幕上打出 1，这是改动前的类的输出，然后会打出一些 2，<strong>这个表示 agentmain 已经被 Attach API 成功附着到 JVM 上，代理程序生效了</strong>，当然，还可以看到“Agent Main Done”字样的输出。</p> <h2 id="本地方法instrument"><a href="#本地方法instrument" class="header-anchor">#</a> 本地方法Instrument</h2> <p>在 JDK 1.5 版本的 Instrumentation 里，并没有对Java本地方法（Native Method）的处理方式，而且在 Java 标准的 JVMTI 之下，并没有办法改变 method signature， 这就使替换本地方法非常地困难。<strong>一个比较直接而简单的想法是，在启动时替换本地代码所在的动态链接库</strong>—— 但是这样，<strong>本质上是一种静态的替换，而不是动态的 Instrumentation</strong>。</p> <p>而且，这样可能需要编译较大数量的动态链接库 —— 比如，我们有三个本地函数，假设每一个都需要一个替换，而在不同的应用之下，可能需要不同的组合，那么如果我们把三个函数都编译在同一个动态链接库之中，最多我们需要 8 个不同的动态链接库来满足需要。当然，我们也可以独立地编译之，那样也需要 6 个动态链接库——无论如何，这种繁琐的方式是不可接受的。</p> <p>在 Java SE 6 中，新的 Native Instrumentation 提出了一个新的 native code 的解析方式，作为原有的 native method 的解析方式的一个补充，来很好地解决了一些问。这就是在新版本的 java.lang.instrument 包里，<strong>我们拥有了对 native 代码的 instrument 方式 —— 设置 prefix</strong>。</p> <p>假设我们有了一个 native 函数，名字叫 nativeMethod，<strong>在运行过程中，我们需要将它指向另外一个函数（需要注意的是，在当前标准的 JVMTI 之下，除了 native 函数名，其他的 signature 需要一致）</strong>。比如我们的 Java 代码是：</p> <div class="language-java line-numbers-mode"><pre class="language-java"><code><span class="token keyword">package</span> <span class="token namespace">nativeTester</span><span class="token punctuation">;</span> 
<span class="token keyword">class</span> nativePrefixTester<span class="token punctuation">{</span> 
     <span class="token keyword">native</span> <span class="token keyword">int</span> <span class="token function">nativeMethod</span><span class="token punctuation">(</span><span class="token keyword">int</span> input<span class="token punctuation">)</span><span class="token punctuation">;</span> 
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><p>那么我们已经实现的本地代码是 :</p> <div class="language-java line-numbers-mode"><pre class="language-java"><code>jint <span class="token class-name">Java_nativeTester_nativeMethod</span><span class="token punctuation">(</span>jclass thiz<span class="token punctuation">,</span> jobject thisObj<span class="token punctuation">,</span> jint input<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>现在我们需要在调用这个函数时，使之指向另外一个函数。那么按照 J2SE 的做法，<strong>我们可以按他的命名方式，加上一个 prefix 作为新的函数名。比如，我们以 &quot;another_&quot; 作为 prefix</strong>，那么我们新的函数是 :</p> <div class="language-java line-numbers-mode"><pre class="language-java"><code>jint <span class="token class-name">Java_nativeTester_another_nativeMethod</span><span class="token punctuation">(</span>jclass thiz<span class="token punctuation">,</span> jobject thisObj<span class="token punctuation">,</span> jint input<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>然后将之编入动态链接库之中。 <strong>现在我们已经有了新的本地函数，接下来就是做 instrument 的设置</strong>。正如以上所说的，<strong>我们可以使用 premain 方式，在虚拟机启动之时就载入 premain 完成 instrument 代理设置。也可以使用 agentmain 方式，去 attach 虚拟机来启动代理</strong>。而设置 native 函数的也是相当简单的 :</p> <div class="language-java line-numbers-mode"><pre class="language-java"><code><span class="token function">premain</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>  <span class="token comment">// 或者也可以在 agentmain 里</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">isNativeMethodPrefixSupported</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span> 
        <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token comment">// 如果无法设置，则返回</span>
    <span class="token function">setNativeMethodPrefix</span><span class="token punctuation">(</span>transformer<span class="token punctuation">,</span><span class="token string">&quot;another_&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 设置 native 函数的 prefix，注意这个下划线必须由用户自己规定</span>
    …
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><p>在这里要注意两个问题：</p> <ol><li><p><strong>不是在任何的情况下都是可以设置 native 函数的 prefix 的；首先，我们要注意到 agent 包之中的 Manifest 所设定的特性 :</strong></p> <div class="language-txt line-numbers-mode"><pre class="language-txt"><code>Can-Set-Native-Method-Prefix: true
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div></li></ol> <p>要注意，<strong>这一个参数都可以影响是否可以设置 native prefix</strong>，而且，在默认的设置之中，这个参数是 false 的，我们需要将之设置成 true（顺便说一句，对 Manifest 之中的属性来说都是大小写无关的，当然，如果给一个不是“true”的值，就会被当作 false 值处理）。</p> <p>当然，我们还需要<strong>确认虚拟机本身是否支持 setNativePrefix</strong>。在 Java API 里，<strong>Instrumentation 类提供了一个函数 isNativePrefix，通过这个函数我们可以知道该功能是否可以实行</strong>。</p> <ol start="2"><li><p><strong>我们可以为每一个 ClassTransformer 加上它自己的 nativeprefix：</strong></p> <p><strong>每一个 ClassTransformer 都可以为同一个 class 做 transform</strong>，因此对于一个 Class 来说，一个 native 函数可能有不同的 prefix，因此对这个函数来说，它可能也有好几种解析方式。 在 Java SE 6 当中，Native prefix 的解释方式如下：<strong>对于某一个 package 内的一个 class 当中的一个 native method 来说，首先，假设我们对这个函数的 transformer 设置了 native 的 prefix “another”，它将这个函数接口解释成</strong> :</p></li></ol> <p>由 Java 的函数接口：</p> <div class="language-java line-numbers-mode"><pre class="language-java"><code><span class="token keyword">native</span> <span class="token keyword">void</span> <span class="token function">method</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>和上述 prefix &quot;another&quot;，去寻找本地代码中的函数：</p> <div class="language-java line-numbers-mode"><pre class="language-java"><code><span class="token comment">// 请注意 prefix 在函数名中出现的位置！</span>
<span class="token keyword">void</span> <span class="token class-name">Java_package_class_another_method</span><span class="token punctuation">(</span>jclass theClass<span class="token punctuation">,</span> jobject thiz<span class="token punctuation">)</span><span class="token punctuation">;</span> 
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><p>一旦可以找到，那么调用这个函数，整个解析过程就结束了；<strong>如果没有找到，那么虚拟机将会做进一步的解析工作。我们将利用 Java native 接口最基本的解析方式 , 去找本地代码中的函数</strong> :</p> <div class="language-java line-numbers-mode"><pre class="language-java"><code><span class="token keyword">void</span> <span class="token class-name">Java_package_class_method</span><span class="token punctuation">(</span>jclass theClass<span class="token punctuation">,</span> jobject thiz<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>如果找到，则执行之。否则，因为没有任何一个合适的解析方式，于是宣告这个过程失败。 那么如果有多个 transformer，同时每一个都有自己的 prefix，又该如何解析呢？<strong>事实上，虚拟机是按 transformer 被加入到的 Instrumentation 之中的次序去解析的（还记得我们最基本的 addTransformer 方法吗？）</strong>。 假设我们有三个 transformer 要被加入进来，他们的次序和相对应的 prefix 分别为：transformer1 和“prefix1_”，transformer2 和 “prefix2_”，transformer3 和 “prefix3_”。那么，虚拟机会首先做的就是将接口解析为 :</p> <div class="language-java line-numbers-mode"><pre class="language-java"><code><span class="token keyword">native</span> <span class="token keyword">void</span> <span class="token function">prefix1_prefix2_prefix3_native_method</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>然后去找它相对应的 native 代码。 但是如果第二个 transformer（transformer2）没有设定 prefix，那么很简单，我们得到的解析是：</p> <div class="language-java line-numbers-mode"><pre class="language-java"><code><span class="token keyword">native</span> <span class="token keyword">void</span> <span class="token function">prefix1_prefix3_native_method</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>这个方式简单而自然。 当然，对于多个 prefix 的情况，我们还要注意一些复杂的情况。比如，假设我们有一个 native 函数接口是：</p> <div class="language-java line-numbers-mode"><pre class="language-java"><code><span class="token keyword">native</span> <span class="token keyword">void</span> <span class="token function">native_method</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>然后我们为它设置了两个 prefix，比如 &quot;wrapped_&quot; 和 &quot;wrapped2_&quot;，那么，我们得到的是什么呢？</p> <div class="language-java line-numbers-mode"><pre class="language-java"><code><span class="token comment">// 这个函数名正确吗？</span>
<span class="token keyword">void</span> <span class="token class-name">Java_package_class_wrapped_wrapped2_method</span><span class="token punctuation">(</span>jclass theClass<span class="token punctuation">,</span> jobject thiz<span class="token punctuation">)</span><span class="token punctuation">;</span> 
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><p>答案是否定的，<strong>因为事实上，对 Java 中 native 函数的接口到 native 中的映射，有一系列的规定，因此可能有一些特殊的字符要被代入</strong>。而实际中，这个函数的正确的函数名是：</p> <div class="language-java line-numbers-mode"><pre class="language-java"><code><span class="token comment">// 只有这个函数名会被找到</span>
<span class="token keyword">void</span> <span class="token class-name">Java_package_class_wrapped_1wrapped2_1method</span><span class="token punctuation">(</span>jclass theClass<span class="token punctuation">,</span> jobject thiz<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><p>很有趣不是吗？因此如果我们要做类似的工作，<strong>一个很好的建议是首先在 Java 中写一个带 prefix 的 native 接口，用 javah 工具生成一个 c 的 header-file，看看它实际解析得到的函数名是什么，这样我们就可以避免一些不必要的麻烦</strong>。</p> <p>另外一个事实是，与我们的想像不同，<strong>对于两个或者两个以上的 prefix，虚拟机并不做更多的解析；它不会试图去掉某一个 prefix，再来组装函数接口。它做且仅作两次解析</strong>。</p> <p>总之，<strong>新的 native 的 prefix-instrumentation 的方式，改变了以前 Java 中 native 代码无法动态改变的缺点</strong>。在当前，<strong>利用 JNI 来写 native 代码也是 Java 应用中非常重要的一个环节</strong>，因此它的动态化意味着整个 Java 都可以动态改变了 —— <strong>现在我们的代码可以利用加上 prefix 来动态改变 native 函数的指向</strong>。</p> <p>正如上面所说的，如果找不到，虚拟机还会去尝试做标准的解析，这让我们拥有了动态地替换 native 代码的方式，我们可以将许多带不同 prefix 的函数编译在一个动态链接库之中，<strong>而通过 instrument 包的功能，让 native 函数和 Java 函数一样动态改变、动态替换</strong>。 当然，现在的 native 的 instrumentation 还有一些限制条件，比如，不同的 transformer 会有自己的 native prefix，就是说，<strong>每一个 transformer 会负责他所替换的所有类而不是特定类的 prefix</strong> —— 因此这个粒度可能不够精确。</p> <h2 id="bootclasspath-systemclasspath-的动态增补"><a href="#bootclasspath-systemclasspath-的动态增补" class="header-anchor">#</a> BootClassPath / SystemClassPath 的动态增补</h2> <p>我们知道，通过设置系统参数或者通过虚拟机启动参数，我们可以<strong>设置一个虚拟机运行时的 boot class 加载路径（-Xbootclasspath）和 system class（-cp）加载路径</strong>。当然，我们在运行之后无法替换它。然而，我们也许有时候要需要把某些 jar 加载到 bootclasspath 之中，而我们无法应用上述两个方法；<strong>或者我们需要在虚拟机启动之后来加载某些 jar 进入 bootclasspath</strong>。在 Java SE 6 之中，我们可以做到这一点了。</p> <p>实现这几点很简单，首先，我们依然需要 <strong>确认虚拟机已经支持这个功能</strong>，然后在 premain/agentmain 之中加上需要的 classpath。<strong>我们可以在premain/agentmain方法中 Instrumentation inst 里使用 appendToBootstrapClassLoaderSearch/appendToSystemClassLoaderSearch来完成这个任务</strong>。</p> <p>同时我们可以注意到，<strong>在 agent 的 manifest 里加入 Boot-Class-Path 其实一样可以在动态地载入agent自己的 boot class 路径</strong>，当然，在 Java code 中它可以更加动态方便和智能地完成 —— 我们可以很方便地加入判断和选择成分。</p> <p><strong>在这里我们也需要注意几点：</strong></p> <ol><li>首先，<strong>我们加入到 classpath 的 jar 文件中不应当带有任何和系统的 instrumentation 有关的系统同名类</strong>，不然，一切都陷入不可预料之中 —— 这不是一个工程师想要得到的结果，不是吗？</li> <li>其次，<strong>我们要注意到虚拟机的 ClassLoader 的工作方式，它会记录解析结果</strong>。比如，我们曾经要求读入某个类 someclass，但是失败了，ClassLoader 会记得这一点。即使我们在后面动态地加入了某一个 jar，含有这个类，ClassLoader 依然会认为我们无法解析这个类，与上次出错的相同的错误会被报告。</li> <li>再次我们知道在 Java 语言中有一个系统参数<code>java.class.path</code>，这个 property 里面记录了我们当前的 classpath，但是，我们使用这两个函数，<strong>虽然真正地改变了实际的 classpath，却不会对这个 property 本身产生任何影响</strong>。</li></ol> <blockquote><p>在公开的 JavaDoc 中我们可以发现一个很有意思的事情，Sun 的设计师们告诉我们，<strong>这个功能事实上依赖于 ClassLoader 的 appendtoClassPathForInstrumentation 方法 —— 这是一个非公开的函数，因此我们不建议直接（使用反射等方式）使用它，事实上，instrument 包里的这两个函数已经可以很好的解决我们的问题了</strong>。</p></blockquote> <h2 id="meta-inf-mainifest-mf清单"><a href="#meta-inf-mainifest-mf清单" class="header-anchor">#</a> META-INF/MAINIFEST.MF清单</h2> <p><strong>以下是agent jar文件的Manifest Attributes清单：</strong></p> <blockquote><ol><li><strong>Premain-Class</strong> 如果 JVM 启动时指定了代理，那么此属性指定代理类，<strong>即包含 premain 方法的类</strong>。如果 JVM 启动时指定了代理，那么此属性是必需的。如果该属性不存在，那么 JVM 将中止。<strong>注：此属性是类名，不是文件名或路径</strong>。</li> <li><strong>Agent-Class</strong> 如果实现支持 VM 启动之后某一时刻启动代理的机制，那么此属性指定代理类，<strong>即包含 agentmain 方法的类</strong>。 此属性是必需的，如果不存在，代理将无法启动。<strong>注：这是类名，而不是文件名或路径</strong>。</li> <li><strong>Boot-Class-Path</strong> 设置引导类加载器搜索的路径列表。路径表示目录或库（在许多平台上通常作为 JAR 或 zip 库被引用）。<strong>查找类的特定于平台的机制失败后，引导类加载器会搜索这些路径</strong>。按列出的顺序搜索路径。列表中的路径由一个或多个空格分开。路径使用分层 URI 的路径组件语法。如果该路径以斜杠字符（“/”）开头，则为绝对路径，否则为相对路径。相对路径根据代理 JAR 文件的绝对路径解析。忽略格式不正确的路径和不存在的路径。如果代理是在 VM 启动之后某一时刻启动的，则忽略不表示 JAR 文件的路径。此属性是可选的。</li> <li><strong>Can-Redefine-Classes</strong> 布尔值（true 或 false，与大小写无关）。<strong>是否能重定义此代理所需的类</strong>。true 以外的值均被视为 false。此属性是可选的，默认值为 false。</li> <li><strong>Can-Retransform-Classes</strong> 布尔值（true 或 false，与大小写无关）。<strong>是否能重转换此代理所需的类</strong>。true 以外的值均被视为 false。此属性是可选的，默认值为 false。</li> <li><strong>Can-Set-Native-Method-Prefix</strong> 布尔值（true 或 false，与大小写无关）。<strong>是否能设置此代理所需的本机方法前缀</strong>。true 以外的值均被视为 false。此属性是可选的，默认值为 false。</li></ol></blockquote> <h2 id="instrumentation原理"><a href="#instrumentation原理" class="header-anchor">#</a> Instrumentation原理</h2> <p><code>instrument</code>的底层实现依赖于<code>JVMTI(JVM Tool Interface)</code>，它是JVM暴露出来的一些供用户扩展的接口集合，JVMTI是基于事件驱动的，JVM每执行到一定的逻辑就会调用一些事件的回调接口（如果有的话），这些接口可以供开发者去扩展自己的逻辑。<code>JVMTIAgent</code>是一个利用<code>JVMTI</code>暴露出来的接口提供了代理启动时加载(agent on load)、代理通过attach形式加载(agent on attach)和代理卸载(agent on unload)功能的动态库。而<code>instrument agent</code>可以理解为一类<code>JVMTIAgent</code>动态库，别名是<code>JPLISAgent(Java Programming Language Instrumentation Services Agent)</code>，也就是<strong>专门为java语言编写的插桩服务提供支持的代理</strong>。</p> <h3 id="启动时加载instrument-agent过程"><a href="#启动时加载instrument-agent过程" class="header-anchor">#</a> 启动时加载instrument agent过程：</h3> <ol><li>创建并初始化 JPLISAgent；</li> <li>监听 <code>VMInit</code> 事件，在 JVM 初始化完成之后做下面的事情：
<ol><li>创建 InstrumentationImpl 对象 ；</li> <li>监听 ClassFileLoadHook 事件 ；</li> <li>调用 InstrumentationImpl 的<code>loadClassAndCallPremain</code>方法，在这个方法里会去调用 javaagent 中 MANIFEST.MF 里指定的Premain-Class 类的 premain 方法 ；</li></ol></li> <li>解析 javaagent 中 MANIFEST.MF 文件的参数，并根据这些参数来设置 JPLISAgent 里的一些内容。</li></ol> <h3 id="运行时加载instrument-agent过程"><a href="#运行时加载instrument-agent过程" class="header-anchor">#</a> 运行时加载instrument agent过程：</h3> <p>通过 JVM 的attach机制来请求目标 JVM 加载对应的agent，过程大致如下：</p> <ol><li>创建并初始化JPLISAgent；</li> <li>解析 javaagent 里 MANIFEST.MF 里的参数；</li> <li>创建 InstrumentationImpl 对象；</li> <li>监听 ClassFileLoadHook 事件；</li> <li>调用 InstrumentationImpl 的<code>loadClassAndCallAgentmain</code>方法，在这个方法里会去调用javaagent里 MANIFEST.MF 里指定的<code>Agent-Class</code>类的<code>agentmain</code>方法。</li></ol> <h3 id="instrumentation的局限性"><a href="#instrumentation的局限性" class="header-anchor">#</a> Instrumentation的局限性</h3> <p>大多数情况下，我们使用Instrumentation都是使用其字节码插桩的功能，或者笼统说就是类重定义(Class Redefine)的功能，但是有以下的局限性：</p> <ol><li>premain和agentmain两种方式修改字节码的时机都是类文件加载之后，也就是说必须要带有Class类型的参数，不能通过字节码文件和自定义的类名重新定义一个本来不存在的类。</li> <li>类的字节码修改称为类转换(Class Transform)，类转换其实最终都回归到类重定义Instrumentation#redefineClasses()方法，此方法有以下限制：
<ol><li>新类和老类的父类必须相同；</li> <li>新类和老类实现的接口数也要相同，并且是相同的接口；</li> <li>新类和老类访问符必须一致。 新类和老类字段数和字段名要一致；</li> <li>新类和老类新增或删除的方法必须是private static/final修饰的；</li> <li>可以修改方法体。</li></ol></li></ol> <p>除了上面的方式，如果想要重新定义一个类，可以考虑基于类加载器隔离的方式：创建一个新的自定义类加载器去通过新的字节码去定义一个全新的类，不过也存在只能通过反射调用该全新类的局限性。</p> <h2 id="agent-的应用"><a href="#agent-的应用" class="header-anchor">#</a> Agent 的应用</h2> <p><img src="/assets/img/javaagent8.6ac51d12.png" alt="javaagent8"></p></div></section> <footer class="page-edit"><!----> <div class="last-updated"><span class="prefix">Last Updated: </span> <span class="time">1/28/2022, 8:09:41 AM</span></div></footer> <!----> <div class="comments-wrapper"><!----></div> <ul class="side-bar sub-sidebar-wrapper" style="width:12rem;" data-v-70334359><li class="level-2" data-v-70334359><a href="/blogs/Java/javaagent-guide.html#介绍" class="sidebar-link reco-side-介绍" data-v-70334359>介绍</a></li><li class="level-2" data-v-70334359><a href="/blogs/Java/javaagent-guide.html#基本功能和用法" class="sidebar-link reco-side-基本功能和用法" data-v-70334359>基本功能和用法</a></li><li class="level-2" data-v-70334359><a href="/blogs/Java/javaagent-guide.html#instrument两个核心api" class="sidebar-link reco-side-instrument两个核心api" data-v-70334359>Instrument两个核心API</a></li><li class="level-2" data-v-70334359><a href="/blogs/Java/javaagent-guide.html#jvm启动前静态instrument" class="sidebar-link reco-side-jvm启动前静态instrument" data-v-70334359>JVM启动前静态Instrument</a></li><li class="level-2" data-v-70334359><a href="/blogs/Java/javaagent-guide.html#jvm启动后动态instrument" class="sidebar-link reco-side-jvm启动后动态instrument" data-v-70334359>JVM启动后动态Instrument</a></li><li class="level-2" data-v-70334359><a href="/blogs/Java/javaagent-guide.html#本地方法instrument" class="sidebar-link reco-side-本地方法instrument" data-v-70334359>本地方法Instrument</a></li><li class="level-2" data-v-70334359><a href="/blogs/Java/javaagent-guide.html#bootclasspath-systemclasspath-的动态增补" class="sidebar-link reco-side-bootclasspath-systemclasspath-的动态增补" data-v-70334359>BootClassPath / SystemClassPath 的动态增补</a></li><li class="level-2" data-v-70334359><a href="/blogs/Java/javaagent-guide.html#meta-inf-mainifest-mf清单" class="sidebar-link reco-side-meta-inf-mainifest-mf清单" data-v-70334359>META-INF/MAINIFEST.MF清单</a></li><li class="level-2" data-v-70334359><a href="/blogs/Java/javaagent-guide.html#instrumentation原理" class="sidebar-link reco-side-instrumentation原理" data-v-70334359>Instrumentation原理</a></li><li class="level-3" data-v-70334359><a href="/blogs/Java/javaagent-guide.html#启动时加载instrument-agent过程" class="sidebar-link reco-side-启动时加载instrument-agent过程" data-v-70334359>启动时加载instrument agent过程：</a></li><li class="level-3" data-v-70334359><a href="/blogs/Java/javaagent-guide.html#运行时加载instrument-agent过程" class="sidebar-link reco-side-运行时加载instrument-agent过程" data-v-70334359>运行时加载instrument agent过程：</a></li><li class="level-3" data-v-70334359><a href="/blogs/Java/javaagent-guide.html#instrumentation的局限性" class="sidebar-link reco-side-instrumentation的局限性" data-v-70334359>Instrumentation的局限性</a></li><li class="level-2" data-v-70334359><a href="/blogs/Java/javaagent-guide.html#agent-的应用" class="sidebar-link reco-side-agent-的应用" data-v-70334359>Agent 的应用</a></li></ul></main> <!----></div></div></div></div><div class="global-ui"><div class="back-to-ceiling" style="right:1rem;bottom:6rem;width:2.5rem;height:2.5rem;border-radius:.25rem;line-height:2.5rem;display:none;" data-v-c6073ba8 data-v-c6073ba8><svg t="1574745035067" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5404" class="icon" data-v-c6073ba8><path d="M526.60727968 10.90185116a27.675 27.675 0 0 0-29.21455937 0c-131.36607665 82.28402758-218.69155461 228.01873535-218.69155402 394.07834331a462.20625001 462.20625001 0 0 0 5.36959153 69.94390903c1.00431239 6.55289093-0.34802892 13.13561351-3.76865779 18.80351572-32.63518765 54.11355614-51.75690182 118.55860487-51.7569018 187.94566865a371.06718723 371.06718723 0 0 0 11.50484808 91.98906777c6.53300375 25.50556257 41.68394495 28.14064038 52.69160883 4.22606766 17.37162448-37.73630017 42.14135425-72.50938081 72.80769204-103.21549295 2.18761121 3.04276886 4.15646224 6.24463696 6.40373557 9.22774369a1871.4375 1871.4375 0 0 0 140.04691725 5.34970492 1866.36093723 1866.36093723 0 0 0 140.04691723-5.34970492c2.24727335-2.98310674 4.21612437-6.18497483 6.3937923-9.2178004 30.66633723 30.70611158 55.4360664 65.4791928 72.80769147 103.21549355 11.00766384 23.91457269 46.15860503 21.27949489 52.69160879-4.22606768a371.15156223 371.15156223 0 0 0 11.514792-91.99901164c0-69.36717486-19.13165746-133.82216804-51.75690182-187.92578088-3.42062944-5.66790279-4.76302748-12.26056868-3.76865837-18.80351632a462.20625001 462.20625001 0 0 0 5.36959269-69.943909c-0.00994388-166.08943902-87.32547796-311.81420293-218.6915546-394.09823051zM605.93803103 357.87693858a93.93749974 93.93749974 0 1 1-187.89594924 6.1e-7 93.93749974 93.93749974 0 0 1 187.89594924-6.1e-7z" p-id="5405" data-v-c6073ba8></path><path d="M429.50777625 765.63860547C429.50777625 803.39355007 466.44236686 1000.39046097 512.00932183 1000.39046097c45.56695499 0 82.4922232-197.00623328 82.5015456-234.7518555 0-37.75494459-36.9345906-68.35043303-82.4922232-68.34111062-45.57627738-0.00932239-82.52019037 30.59548842-82.51086798 68.34111062z" p-id="5406" data-v-c6073ba8></path></svg></div><div class="reco-bgm-panel" data-v-b1d3339e><audio id="bgm" src="/music/1.m4a" data-v-b1d3339e></audio> <div class="reco-float-box" style="bottom:200px;z-index:999999;display:none;" data-v-b1d3339e data-v-41bcba48 data-v-b1d3339e><img src="https://y.qq.com/music/photo_new/T002R300x300M0000046Etze42qCxC_1.jpg" data-v-b1d3339e></div> <div class="reco-bgm-box" style="left:10px;bottom:10px;z-index:999999;" data-v-b1d3339e data-v-41bcba48 data-v-b1d3339e><div class="reco-bgm-cover" style="background-image:url(https://y.qq.com/music/photo_new/T002R300x300M0000046Etze42qCxC_1.jpg);" data-v-b1d3339e><div class="mini-operation" style="display:none;" data-v-b1d3339e><i class="reco-bgm reco-bgm-pause" style="display:none;" data-v-b1d3339e></i> <i class="reco-bgm reco-bgm-play" style="display:none;" data-v-b1d3339e></i></div> <div class="falut-message" style="display:none;" data-v-b1d3339e>
          播放失败
        </div></div> <div class="reco-bgm-info" data-v-b1d3339e data-v-41bcba48 data-v-b1d3339e><div class="info-box" data-v-b1d3339e><i class="reco-bgm reco-bgm-music music" data-v-b1d3339e></i>苍穹</div> <div class="info-box" data-v-b1d3339e><i class="reco-bgm reco-bgm-artist" data-v-b1d3339e></i>韩磊</div> <div class="reco-bgm-progress" data-v-b1d3339e><div class="progress-bar" data-v-b1d3339e><div class="bar" data-v-b1d3339e></div></div></div> <div class="reco-bgm-operation" data-v-b1d3339e><i class="reco-bgm reco-bgm-last last" data-v-b1d3339e></i> <i class="reco-bgm reco-bgm-pause pause" style="display:none;" data-v-b1d3339e></i> <i class="reco-bgm reco-bgm-play play" data-v-b1d3339e></i> <i class="reco-bgm reco-bgm-next next" data-v-b1d3339e></i> <i class="reco-bgm reco-bgm-volume1 volume" data-v-b1d3339e></i> <i class="reco-bgm reco-bgm-mute mute" style="display:none;" data-v-b1d3339e></i> <div class="volume-bar" data-v-b1d3339e><div class="bar" data-v-b1d3339e></div></div></div></div> <div class="reco-bgm-left-box" data-v-b1d3339e data-v-41bcba48 data-v-b1d3339e><i class="reco-bgm reco-bgm-left" data-v-b1d3339e></i></div></div></div><!----></div></div>
    <script src="/assets/js/app.b9ccfc55.js" defer></script><script src="/assets/js/4.8390fbcb.js" defer></script><script src="/assets/js/1.3334f995.js" defer></script><script src="/assets/js/16.b75d2036.js" defer></script>
  </body>
</html>
